/**
 * Virtual DOM patching algorithm based on Snabbdom by
 * Simon Friis Vindum (@paldepind)
 * Licensed under the MIT License
 * https://github.com/paldepind/snabbdom/blob/master/LICENSE
 *
 * modified by Evan You (@yyx990803)
 *
 * Not type-checking this because this file is perf-critical and the cost
 * of making flow understand it is not worth it.
 */

import VNode, { cloneVNode } from './vnode'
import config from '../config'
import { SSR_ATTR } from 'shared/constants'
import { registerRef } from './modules/ref'
import { traverse } from '../observer/traverse'
import { activeInstance } from '../instance/lifecycle'
import { isTextInputType } from 'web/util/element'

import {
  warn,
  isDef,
  isUndef,
  isTrue,
  makeMap,
  isRegExp,
  isPrimitive
} from '../util/index'

export const emptyNode = new VNode('', {}, [])

const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
/**
 * 判断2个vnode是否是sameVnode，要求是：key相同
 */
function sameVnode (a, b) {
  return (
    a.key === b.key && (
      (  // 同步组件
        a.tag === b.tag &&  // tag是否相同
        a.isComment === b.isComment &&  // 是否是注释类型
        isDef(a.data) === isDef(b.data) &&  // data不为undefined
        sameInputType(a, b)  // input类型是否相同
      ) || (  // 异步组件
        isTrue(a.isAsyncPlaceholder) &&  
        a.asyncFactory === b.asyncFactory &&  // asyncFactory是否相同
        isUndef(b.asyncFactory.error)
      )
    )
  )
}

function sameInputType (a, b) {
  if (a.tag !== 'input') return true
  let i
  const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
  const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
  return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
}

function createKeyToOldIdx (children, beginIdx, endIdx) {
  let i, key
  const map = {}
  for (i = beginIdx; i <= endIdx; ++i) {
    key = children[i].key
    if (isDef(key)) map[key] = i
  }
  return map
}

export function createPatchFunction (backend) {
  let i, j
  const cbs = {}

  const { modules, nodeOps } = backend

  for (i = 0; i < hooks.length; ++i) {
    cbs[hooks[i]] = []
    for (j = 0; j < modules.length; ++j) {
      if (isDef(modules[j][hooks[i]])) {
        cbs[hooks[i]].push(modules[j][hooks[i]])
      }
    }
  }

  function emptyNodeAt (elm) {
    return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
  }

  function createRmCb (childElm, listeners) {
    function remove () {
      if (--remove.listeners === 0) {
        removeNode(childElm)
      }
    }
    remove.listeners = listeners
    return remove
  }

  function removeNode (el) {
    const parent = nodeOps.parentNode(el)
    // element may have already been removed due to v-html / v-text
    if (isDef(parent)) {
      nodeOps.removeChild(parent, el)
    }
  }

  function isUnknownElement (vnode, inVPre) {
    return (
      !inVPre &&
      !vnode.ns &&
      !(
        config.ignoredElements.length &&
        config.ignoredElements.some(ignore => {
          return isRegExp(ignore)
            ? ignore.test(vnode.tag)
            : ignore === vnode.tag
        })
      ) &&
      config.isUnknownElement(vnode.tag)
    )
  }

  let creatingElmInVPre = 0
  /** 
   * createElm 的作用是通过虚拟节点创建真实的 DOM 并插入到它的父节点中
    node可能是：
    1：普通html标签节点vnode(包括tag标签、注释节点、文本节点)，则直接转为真实dom
    2、组件vnode，调用createComponent实例化子组件，走子组件初始化流程，完成后，挂载到父dom上
  */
  function createElm (
    vnode,
    insertedVnodeQueue,
    parentElm,
    refElm,
    nested,
    ownerArray,
    index
  ) {
    if (isDef(vnode.elm) && isDef(ownerArray)) {
      // This vnode was used in a previous render!
      // now it's used as a new node, overwriting its elm would cause
      // potential patch errors down the road when it's used as an insertion
      // reference node. Instead, we clone the node on-demand before creating
      // associated DOM element for it.
      vnode = ownerArray[index] = cloneVNode(vnode)
    }

    vnode.isRootInsert = !nested // for transition enter check
    // 1、vnode节点：如果vnode.data有对应的componentVnodeHooks，则表示它是一个自定义的子组件vnode，我们需要开始子组件的初始化过程：
    // createComponent函数: 1：new vnode.componentOptions.Ctor(),创建子组件实例，2：$mount,挂载到父dom节点中
    // 即'子组件初始化'的入口：vm._update(vnode) -> patch -> createElm -> createComponent -> 
    // --> componentVNodeHooks.init(create-component.js,38行:componentVNodeHooks内的init钩子) --> 
    // --> createComponentInstanceForVnode(创建子组件实例)  --> child._init(子组件初始化) -> child.$mount(子组件挂载)
    // --> 子组件挂载完成后，将子组件的vnode.elm，插入到parentElm中，即：insert(parentElm, vnode.elm, refElm)
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }

    // 2、html节点
    const data = vnode.data
    const children = vnode.children
    const tag = vnode.tag
    // 1 内置的html标签节点
    if (isDef(tag)) {
      if (process.env.NODE_ENV !== 'production') {
        if (data && data.pre) {
          creatingElmInVPre++
        }
        if (isUnknownElement(vnode, creatingElmInVPre)) {
          warn(
            'Unknown custom element: <' + tag + '> - did you ' +
            'register the component correctly? For recursive components, ' +
            'make sure to provide the "name" option.',
            vnode.context
          )
        }
      }
      // 创建dom对象：elm
      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode)
      setScope(vnode)

      /* istanbul ignore if */
      if (__WEEX__) {
        // in Weex, the default insertion order is parent-first.
        // List items can be optimized to use children-first insertion
        // with append="tree".
        const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
        if (!appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue)
          }
          insert(parentElm, vnode.elm, refElm)
        }
        createChildren(vnode, children, insertedVnodeQueue)
        if (appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue)
          }
          insert(parentElm, vnode.elm, refElm)
        }
      } else {
        /**
         * createChildren:遍历子虚拟节点，递归调用 createElm，
        * 这是一种常用的深度优先的遍历算法，这里要注意的一点是在遍历过程中会把 vnode.elm 作为父容器的 DOM 节点占位符传入。
         */
        createChildren(vnode, children, insertedVnodeQueue)
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        // 将当前elm这个dom对象，插入到parentElm里(如果refElem存在，则插入到refElem前面)
        // insert方法： src/platforms/web/runtime/node-ops.js
        insert(parentElm, vnode.elm, refElm)
      }

      if (process.env.NODE_ENV !== 'production' && data && data.pre) {
        creatingElmInVPre--
      }
    } else if (isTrue(vnode.isComment)) {
      // 2 注释节点
      vnode.elm = nodeOps.createComment(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    } else {
      // 3 文本节点
      vnode.elm = nodeOps.createTextNode(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    }
  }
  // 创建子组件实例并挂载到父dom节点中
  /**
   * 1、执行vnode的init钩子 -> 创建子组件实例 -> 子组件挂载 ->将挂载后的dom赋值给vm.$el
   * 2、initComponent:缓存vm.$el到 vnode.elm上，同时将vnode放入insertedVnodeQueue中
   * 3、insert: 将子组件dom插入插入parentElm中
   * 4、等待所有子组件挂载完成，执行insertedVnodeQueue中各个vnode的insert钩子，触发子组件的mounted方法
   */
  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data
    if (isDef(i)) {
      // 初次渲染时，vnode.componentInstance为undefined(vnode.componentInstance赋值是在243行的init钩子中，子组件初始化前执行的)
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
      // data的hook，是在创建vnode时：
      // vnode.data.hook，是在调用_createElement- > createComponent -> installComponentHooks中赋值的
      if (isDef(i = i.hook) && isDef(i = i.init)) {
        // 子组件实例化：src\core\vdom\create-component.js,37行 
        // componentVNodeHooks.init钩子 --> child = createComponentInstanceForVnode --> child.$mount(undefined, false)
        i(vnode, false /* hydrating */)
      }
      // after calling the init hook, if the vnode is a child component
      // it should've created a child instance and mounted it. the child
      // component also has set the placeholder vnode's elm.
      // in that case we can just return the element and be done.
      if (isDef(vnode.componentInstance)) {
        // 缓存真实dom到vnode的elm属性上
        initComponent(vnode, insertedVnodeQueue)
        // 子组件挂载完成后，将子组件的vnode.elm，插入到parentElm中
        insert(parentElm, vnode.elm, refElm)
        if (isTrue(isReactivated)) {
          // 处理vnode.data.keepAlive为true的vnode
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true
      }
    }
  }

  function initComponent (vnode, insertedVnodeQueue) {
    if (isDef(vnode.data.pendingInsert)) {
      insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
      vnode.data.pendingInsert = null
    }
    // 缓存真实dom到vnode的elm属性上(子组件patch阶段，子组件挂载后生成的真实dom，存放在vm实例的$el上)
    vnode.elm = vnode.componentInstance.$el  // vm实例上可以通过$el拿到真实的dom，并赋值给vnode.elm
    if (isPatchable(vnode)) {
      invokeCreateHooks(vnode, insertedVnodeQueue)
      setScope(vnode)
    } else {
      // empty component root.
      // skip all element-related modules except for ref (#3455)
      registerRef(vnode)
      // make sure to invoke the insert hook
      insertedVnodeQueue.push(vnode)
    }
  }

  function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i
    // hack for #4339: a reactivated component with inner transition
    // does not trigger because the inner node's created hooks are not called
    // again. It's not ideal to involve module-specific logic in here but
    // there doesn't seem to be a better way to do it.
    let innerNode = vnode
    while (innerNode.componentInstance) {
      innerNode = innerNode.componentInstance._vnode
      if (isDef(i = innerNode.data) && isDef(i = i.transition)) {
        for (i = 0; i < cbs.activate.length; ++i) {
          cbs.activate[i](emptyNode, innerNode)
        }
        insertedVnodeQueue.push(innerNode)
        break
      }
    }
    // unlike a newly created component,a reactivated keep-alive component doesn't insert itself
    // 与新创建的component不同，一个reactived keep-alive组件不会插入自己

    // 将keep-alive的组件vnode.elm，插入到parentElm中
    insert(parentElm, vnode.elm, refElm)
  }
  /**
   * 将elm插入到参考节点前(后)
   * @param {*} parent 父节点
   * @param {*} elm 当前节点
   * @param {*} ref 参考节点
   * 如果有ref参考节点，并且ref.parent == parent,则将elm插入到ref前面， 否则，将elm插入到parent尾部
   */
  function insert (parent, elm, ref) {
    if (isDef(parent)) {
      if (isDef(ref)) {
        if (nodeOps.parentNode(ref) === parent) {
          nodeOps.insertBefore(parent, elm, ref)
        }
      } else {
        nodeOps.appendChild(parent, elm)
      }
    }
  }

  function createChildren (vnode, children, insertedVnodeQueue) {
    if (Array.isArray(children)) {
      if (process.env.NODE_ENV !== 'production') {
        checkDuplicateKeys(children)
      }
      for (let i = 0; i < children.length; ++i) {
        // 将当前vnode的elm，作为父节点传入createElm
        createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
      }
    } else if (isPrimitive(vnode.text)) {
      nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
    }
  }

  function isPatchable (vnode) {
    while (vnode.componentInstance) {
      vnode = vnode.componentInstance._vnode
    }
    return isDef(vnode.tag)
  }

  function invokeCreateHooks (vnode, insertedVnodeQueue) {
    for (let i = 0; i < cbs.create.length; ++i) {
      cbs.create[i](emptyNode, vnode)
    }
    i = vnode.data.hook // Reuse variable
    if (isDef(i)) {
      if (isDef(i.create)) i.create(emptyNode, vnode)
      if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
    }
  }

  // set scope id attribute for scoped CSS.
  // this is implemented as a special case to avoid the overhead
  // of going through the normal attribute patching process.
  function setScope (vnode) {
    let i
    if (isDef(i = vnode.fnScopeId)) {
      nodeOps.setStyleScope(vnode.elm, i)
    } else {
      let ancestor = vnode
      while (ancestor) {
        if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {
          nodeOps.setStyleScope(vnode.elm, i)
        }
        ancestor = ancestor.parent
      }
    }
    // for slot content they should also get the scopeId from the host instance.
    if (isDef(i = activeInstance) &&
      i !== vnode.context &&
      i !== vnode.fnContext &&
      isDef(i = i.$options._scopeId)
    ) {
      nodeOps.setStyleScope(vnode.elm, i)
    }
  }

  function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) {
    for (; startIdx <= endIdx; ++startIdx) {
      createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm, false, vnodes, startIdx)
    }
  }
  // 调用vnode的destroy钩子 -> vm.$destroy() -> callHook(vm, 'destroyed')
  function invokeDestroyHook (vnode) {
    let i, j
    const data = vnode.data
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
      for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
    }
    if (isDef(i = vnode.children)) {
      for (j = 0; j < vnode.children.length; ++j) {
        invokeDestroyHook(vnode.children[j])
      }
    }
  }

  function removeVnodes (vnodes, startIdx, endIdx) {
    for (; startIdx <= endIdx; ++startIdx) {
      const ch = vnodes[startIdx]
      if (isDef(ch)) {
        if (isDef(ch.tag)) {
          removeAndInvokeRemoveHook(ch)
          invokeDestroyHook(ch)
        } else { // Text node
          removeNode(ch.elm)
        }
      }
    }
  }

  function removeAndInvokeRemoveHook (vnode, rm) {
    if (isDef(rm) || isDef(vnode.data)) {
      let i
      const listeners = cbs.remove.length + 1
      if (isDef(rm)) {
        // we have a recursively passed down rm callback
        // increase the listeners count
        rm.listeners += listeners
      } else {
        // directly removing
        rm = createRmCb(vnode.elm, listeners)
      }
      // recursively invoke hooks on child component root node
      if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {
        removeAndInvokeRemoveHook(i, rm)
      }
      for (i = 0; i < cbs.remove.length; ++i) {
        cbs.remove[i](vnode, rm)
      }
      if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {
        i(vnode, rm)
      } else {
        rm()
      }
    } else {
      removeNode(vnode.elm)
    }
  }

  // dom-diff过程，双指针移动对比(老的与新的比较): 头与头比较、尾与尾比较、头与尾比较、尾与头比较、乱序对比(根据key去老的children中查找vnode)
  /**
   * 
   * @param {*} parentElm 父节点
   * @param {*} oldCh 老的孩子vnode
   * @param {*} newCh 新的孩子vnode
   * @param {*} insertedVnodeQueue 
   * @param {*} removeOnly 
   */
  function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0
    let newStartIdx = 0
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx, idxInOld, vnodeToMove, refElm

    // removeOnly is a special flag used only by <transition-group>
    // to ensure removed elements stay in correct relative positions
    // during leaving transitions
    const canMove = !removeOnly

    if (process.env.NODE_ENV !== 'production') {
      checkDuplicateKeys(newCh)
    }

    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        // 如果老的开始vnode是undefined，则跳过该节点，并且老的指针后移(从前往后对比)。
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) {
        // 如果老的结束节点是undefined，则跳过该节点，并且老的指针前移(从后往前对比)。
        oldEndVnode = oldCh[--oldEndIdx]
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        // 1）老的头，新的头比较：patch完成后，同时右移
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 2）老的尾，新的尾比较：patch完成后，同时左移
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        // 3）老的头，新的尾比较：patch完成后，老的右移,新的左移。然后将"老的头"，插入"老的尾"的"下一个兄弟节点"之前.
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        // 在oldEndVnode.elm的下一个兄弟节点前，插入oldStartVnode.elm（即，将当前vnode.elm，插入到"oldEndVnode.elm的下一个兄弟节点"之前）
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        oldStartVnode = oldCh[++oldStartIdx] // 老的右移
        newEndVnode = newCh[--newEndIdx]  // 新的左移
      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        // 4）老的尾，新的头比较：patch完成后，老的左移,新右的移。然将"老的尾"，插入"新的头"之前。
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        // 在oldStartVnode前面，插入oldEndVnode（即，将当前vnode.elm插入到oldStartVnode.elm前面）
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]  // 老的左移x
        newStartVnode = newCh[++newStartIdx]  // 新的右移
      } else {
        // 5）乱序对比
        // 根据key，从老的children中查找vnode
        // 将老的children创建一个map映射： map = { key1: index1, key2: index2,...},方便后续从老的children中查找vnode。
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
        if (isUndef(idxInOld)) { // New element
          // 1) 如果没有找到，则创建新元素，并插入到当前老节点指针的前面
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        } else {
          // 2) 如果找到了，则将这个节点移动到老节点当前之前的前面， 并且将老的vnode置为undefined。注意，此时vnode数组中有可能有undefined的元素
          vnodeToMove = oldCh[idxInOld]
          if (sameVnode(vnodeToMove, newStartVnode)) { // 相同vnode
            // 更新节点
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
            // 置空
            oldCh[idxInOld] = undefined 
            // 移动
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          } else {
            // same key but different element. treat as new element
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
          }
        }
        newStartVnode = newCh[++newStartIdx]
      }
    }
    if (oldStartIdx > oldEndIdx) { // 新的多
      // 说明新的children还有剩余vnode，依次创建并添加元素即可。
      // 注意，此时可能插入的元素是在末尾，也可能是在开头
      // 如果新的尾元素+1的下一个元素是null，则是向尾部插入。否则就是向头部插入
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) { // 老的多
      // 说明老的children中还有剩余vnode，依次移除即可。
      removeVnodes(oldCh, oldStartIdx, oldEndIdx)
    }
  }

  function checkDuplicateKeys (children) {
    const seenKeys = {}
    for (let i = 0; i < children.length; i++) {
      const vnode = children[i]
      const key = vnode.key
      if (isDef(key)) {
        if (seenKeys[key]) {
          warn(
            `Duplicate keys detected: '${key}'. This may cause an update error.`,
            vnode.context
          )
        } else {
          seenKeys[key] = true
        }
      }
    }
  }

  function findIdxInOld (node, oldCh, start, end) {
    for (let i = start; i < end; i++) {
      const c = oldCh[i]
      if (isDef(c) && sameVnode(node, c)) return i
    }
  }

  // vnode对比更新，即dom-diff过程
  function patchVnode (
    oldVnode,
    vnode,
    insertedVnodeQueue,
    ownerArray,
    index,
    removeOnly
  ) {
    if (oldVnode === vnode) {
      return
    }

    if (isDef(vnode.elm) && isDef(ownerArray)) {
      // clone reused vnode
      vnode = ownerArray[index] = cloneVNode(vnode)
    }
    // 复用dom节点(复用老的vnode的dom节点)
    const elm = vnode.elm = oldVnode.elm

    if (isTrue(oldVnode.isAsyncPlaceholder)) {
      if (isDef(vnode.asyncFactory.resolved)) {
        hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
      } else {
        vnode.isAsyncPlaceholder = true
      }
      return
    }

    // reuse element for static trees.
    // note we only do this if the vnode is cloned -
    // if the new node is not cloned it means the render functions have been
    // reset by the hot-reload-api and we need to do a proper re-render.
    if (isTrue(vnode.isStatic) &&
      isTrue(oldVnode.isStatic) &&
      vnode.key === oldVnode.key &&
      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
    ) {
      vnode.componentInstance = oldVnode.componentInstance
      return
    }

    let i
    const data = vnode.data
    // 1、如果是组件vnode：先走vnode.prepatch钩子，用于更新子组件的propsData、listeners、attrs等
    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
      // 调用vnode的prepatch钩子(即componentVNodeHooks.prepatch), 其内部调用'updateChildComponent'更新子组件
      i(oldVnode, vnode)
    }
    // 2、patch孩子节点
    const oldCh = oldVnode.children  // 旧vnode的children
    const ch = vnode.children        // 新vnode的children
    if (isDef(data) && isPatchable(vnode)) { // 3、patch属性数据
      // 调用updateAttrs、updateClass、updateDOMListeners、updateDOMProps、updateStyle、updateDirectives，
      // 更新attrs、class、listeners，domProps、style和directives
      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode) // vnode update钩子
    }
    // 4、非文本节点：对比children
    if (isUndef(vnode.text)) {
      if (isDef(oldCh) && isDef(ch)) {
        // 新旧vnode的children都存在，调用updateChildren对子节点进行diff操作(dom diff核心)
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
      } else if (isDef(ch)) {
        // 只有新vnode有children，删除旧的节点，将新的children添加进去
        if (process.env.NODE_ENV !== 'production') {
          checkDuplicateKeys(ch)
        }
        // 清空老节点文本内容
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        // 给当前节点elm添加子节点
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
        // 只有旧vnode有children，删除旧的子节点, 进入vnode的destroy钩子，进入子组件的销毁流程
        removeVnodes(oldCh, 0, oldCh.length - 1)
      } else if (isDef(oldVnode.text)) {  // 都没有子节点，移除节点文本
        /*当新老节点都无子节点的时候，只是文本的替换，因为这个逻辑中新节点text不存在，所以直接去除ele的文本*/
        nodeOps.setTextContent(elm, '')
      }
    } else if (oldVnode.text !== vnode.text) {
      // 文本节点：但内容不同，直接调用dom api更新文本
      nodeOps.setTextContent(elm, vnode.text)
    }
    if (isDef(data)) {
      // todo: 调用postpatch钩子
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }

  function invokeInsertHook (vnode, queue, initial) {
    // delay insert hooks for component root nodes, invoke them after the
    // element is really inserted
    if (isTrue(initial) && isDef(vnode.parent)) {
      vnode.parent.data.pendingInsert = queue
    } else {
      for (let i = 0; i < queue.length; ++i) {
        queue[i].data.hook.insert(queue[i])
      }
    }
  }

  let hydrationBailed = false
  // list of modules that can skip create hook during hydration because they
  // are already rendered on the client or has no need for initialization
  // Note: style is excluded because it relies on initial clone for future
  // deep updates (#7063).
  const isRenderedModule = makeMap('attrs,class,staticClass,staticStyle,key')

  // Note: this is a browser-only function so we can assume elms are DOM nodes.
  function hydrate (elm, vnode, insertedVnodeQueue, inVPre) {
    let i
    const { tag, data, children } = vnode
    inVPre = inVPre || (data && data.pre)
    vnode.elm = elm

    if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) {
      vnode.isAsyncPlaceholder = true
      return true
    }
    // assert node match
    if (process.env.NODE_ENV !== 'production') {
      if (!assertNodeMatch(elm, vnode, inVPre)) {
        return false
      }
    }
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode, true /* hydrating */)
      if (isDef(i = vnode.componentInstance)) {
        // child component. it should have hydrated its own tree.
        initComponent(vnode, insertedVnodeQueue)
        return true
      }
    }
    if (isDef(tag)) {
      if (isDef(children)) {
        // empty element, allow client to pick up and populate children
        if (!elm.hasChildNodes()) {
          createChildren(vnode, children, insertedVnodeQueue)
        } else {
          // v-html and domProps: innerHTML
          if (isDef(i = data) && isDef(i = i.domProps) && isDef(i = i.innerHTML)) {
            if (i !== elm.innerHTML) {
              /* istanbul ignore if */
              if (process.env.NODE_ENV !== 'production' &&
                typeof console !== 'undefined' &&
                !hydrationBailed
              ) {
                hydrationBailed = true
                console.warn('Parent: ', elm)
                console.warn('server innerHTML: ', i)
                console.warn('client innerHTML: ', elm.innerHTML)
              }
              return false
            }
          } else {
            // iterate and compare children lists
            let childrenMatch = true
            let childNode = elm.firstChild
            for (let i = 0; i < children.length; i++) {
              if (!childNode || !hydrate(childNode, children[i], insertedVnodeQueue, inVPre)) {
                childrenMatch = false
                break
              }
              childNode = childNode.nextSibling
            }
            // if childNode is not null, it means the actual childNodes list is
            // longer than the virtual children list.
            if (!childrenMatch || childNode) {
              /* istanbul ignore if */
              if (process.env.NODE_ENV !== 'production' &&
                typeof console !== 'undefined' &&
                !hydrationBailed
              ) {
                hydrationBailed = true
                console.warn('Parent: ', elm)
                console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children)
              }
              return false
            }
          }
        }
      }
      if (isDef(data)) {
        let fullInvoke = false
        for (const key in data) {
          if (!isRenderedModule(key)) {
            fullInvoke = true
            invokeCreateHooks(vnode, insertedVnodeQueue)
            break
          }
        }
        if (!fullInvoke && data['class']) {
          // ensure collecting deps for deep class bindings for future updates
          traverse(data['class'])
        }
      }
    } else if (elm.data !== vnode.text) {
      elm.data = vnode.text
    }
    return true
  }

  function assertNodeMatch (node, vnode, inVPre) {
    if (isDef(vnode.tag)) {
      return vnode.tag.indexOf('vue-component') === 0 || (
        !isUnknownElement(vnode, inVPre) &&
        vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase())
      )
    } else {
      return node.nodeType === (vnode.isComment ? 8 : 3)
    }
  }
  // patch函数
  // 1、oldVnode是一个dom，初次渲染流程
  // 2、oldVnode是一个vnode，更新流程
  // 3、vnode(新的vnode)不存在，卸载流程
  /**
   * 对于第一次挂载，oldVnode是一个真实的dom元素
    * 第一步： mount阶段
    *  1 如果有template;
    *  2 没有template，将el.outerHTML作为template;
    * 第二步：patch阶段
    *  将template转为render函数 -> vm._render()生成vnode -> vm_update(vnode) -> patch;
    *  patch时，基于vnode生成真实dom节点，并插入el.nextSibling的前面, 然后删除el这个dom;
    * 
   */
  return function patch (oldVnode, vnode, hydrating, removeOnly) {
    // 4、组件销毁流程
    if (isUndef(vnode)) { // 新节点不存在，走销毁流程
      // 新的vnode不存在，则认为是要卸载之前的节点($destroy卸载流程)
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []
    // 2、子组件渲染流程
    // 老节点不存在：子组件渲染入口，由于子组件调用$mount挂载时,由于$el为undefined，即oldVnode为undefined，进入该分支
    if (isUndef(oldVnode)) { // 老节点不存在，走子组件的渲染流程
      // empty mount (likely as component), create new root element(空挂载，比如component组件，创建新的节点)
      // 空挂载(比如组件挂载，$mount(undefined, false))，则创建一个根元素
      // oldVnode为undefined: 包括子节点初始化挂载时，此时vm.$el为undefined（参考vdom/create-component.js，56行子组件挂载过程）
      isInitialPatch = true
      // 生成子组件的 DOM Tree（子组件实例首次patch，oldVnode 为 undefined）
      createElm(vnode, insertedVnodeQueue)
    } else { // 新老节点都存在
      // 根节点渲染：$el为dom节点，isRealElement为true
      const isRealElement = isDef(oldVnode.nodeType)
      // 3、组件更新流程
      // sameVnode：判断新旧节点oldVnode与vnode是否是同一个vnode
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // 第N次渲染(更新流程)
        // patch existing root node
        // 1、新旧节点相同
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
      } else {
        // 1、初次渲染流程
        if (isRealElement) { // 初次渲染流程，oldVnode是一个真实的dom元素（dom app根节点挂载时）
          // mounting to a real element
          // check if this is server-rendered content and if we can perform
          // a successful hydration.
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) { // 服务端渲染
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if (process.env.NODE_ENV !== 'production') {
              warn(
                'The client-side rendered virtual DOM tree is not matching ' +
                'server-rendered content. This is likely caused by incorrect ' +
                'HTML markup, for example nesting block-level elements inside ' +
                '<p>, or missing <tbody>. Bailing hydration and performing ' +
                'full client-side render.'
              )
            }
          }
          // either not server-rendered, or hydration failed.
          // create an empty node and replace it
          // 第一次渲染时(根组件调用$mount时)，oldVnode为dom对象(app对应的dom对象)，将它转为一个空的vnode对象,同时，复用老的elm对象
          oldVnode = emptyNodeAt(oldVnode)
        }

        // replacing existing element
        // oldElm: 旧的dom对象oldElm。elm属性是在上一行执行emptyNodeAt函数时，在new Vnode()初始化的时候赋值的
        const oldElm = oldVnode.elm
        // 获取oldElm的父节点，初始化时，parentElm为body
        const parentElm = nodeOps.parentNode(oldElm)

        // createElm：根据虚拟节点vnode生成一个真实的dom对象，并插入到它的父节点中
        // 这是一个递归调用的过程，包括vnode的children(vnode数组)也是如此
        // create new node
        // 2、新旧节点不同，更新逻辑为：1）创建新节点 --> 2）更新父的占位符节点 --> 3）删除旧节点
            // 2-1）以当前旧节点为参考节点，创建新的节点，并插入到 DOM 中
        createElm(
          vnode,
          insertedVnodeQueue,
          // extremely rare edge case: do not insert if old element is in a
          // leaving transition. Only happens when combining transition +
          // keep-alive + HOCs. (#4590)
          oldElm._leaveCb ? null : parentElm,
          nodeOps.nextSibling(oldElm)
        )

        // update parent placeholder node element, recursively
          // 2-2）递归更新父占位符节点元素
        if (isDef(vnode.parent)) {
          // 父节点vnode
          let ancestor = vnode.parent
          const patchable = isPatchable(vnode)
          while (ancestor) {
            for (let i = 0; i < cbs.destroy.length; ++i) {
              cbs.destroy[i](ancestor)
            }
            ancestor.elm = vnode.elm
            if (patchable) {
              for (let i = 0; i < cbs.create.length; ++i) {
                cbs.create[i](emptyNode, ancestor)
              }
              // #6513
              // invoke insert hooks that may have been merged by create hooks.
              // e.g. for directives that uses the "inserted" hook.
              const insert = ancestor.data.hook.insert
              if (insert.merged) {
                // start at index 1 to avoid re-invoking component mounted hook
                for (let i = 1; i < insert.fns.length; i++) {
                  insert.fns[i]()
                }
              }
            } else {
              registerRef(ancestor)
            }
            ancestor = ancestor.parent
          }
        }

        // destroy old node
        // 2-3）删除旧节点
        if (isDef(parentElm)) {
          removeVnodes([oldVnode], 0, 0)
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode)
        }
      }
    }

    // https://elisse.cn/vue-analysis/v2/data-driven/update.html#总结
    // patch完成后，触发componentVnodeHooks的insert钩子，此时子组件的mounted钩子触发，即调用callhook('mounted'), 至此子组件挂载完成
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }
}
